package com.mt.mall.service.Order.impl;

import com.alibaba.fastjson.JSONObject;
import com.mt.common.core.config.RabbitMqConfig;
import com.mt.common.core.security.RedisUtil;
import com.mt.common.core.utils.PrimaryGenerator;
import com.mt.common.core.utils.RabbitMqUtil;
import com.mt.common.core.utils.RedissonUtil;
import com.mt.common.core.web.JsonResult;
import com.mt.common.system.service.AttachmentService;
import com.mt.mall.dao.Order.OrderDao;

import com.mt.common.core.exception.BusinessException;
import com.mt.common.core.web.base.PageDTO;
import com.mt.common.core.web.base.PageResultDTO;
import com.mt.common.core.web.base.BaseEntity;
import com.mt.common.core.web.BaseService;
import com.mt.mall.entity.Order.Order;
import com.mt.mall.entity.User.ShippingAddress;
import com.mt.mall.service.good.SpecificationStockService;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.mt.mall.service.Order.OrderService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
public class OrderServiceBean extends BaseService implements OrderService {

    private static Logger logger = LogManager.getLogger();

    @Autowired
    private OrderDao orderDao;

    @Resource
    private RedisTemplate<String, List<Order>> redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private SpecificationStockService specificationStockService;

    @Autowired
    private RedissonUtil redissonUtil;

    @Autowired
    private RabbitMqUtil rabbitMqUtil;

    @Autowired
    private AttachmentService attachmentService;

    /**
     * 根据分页参数查询订单集合
     *
     * @param pageDTO 分页条件
     */
    @Override
    public PageResultDTO findOrders(PageDTO pageDTO) {
        pageDTO.setStartIndex((pageDTO.getCurrentPage() - 1) * pageDTO.getPageSize());
        //TODO:请在此校验参数的合法性
        this.validateFindOrders(pageDTO);
        if(this.getStoreId()!=-1){
            pageDTO.getFilters().put("shopId",this.getStoreId());
            pageDTO.getFilterTypes().put("shopId","单值");
        }
        List<Order> orderDTOS = this.orderDao.findOrders(pageDTO);
        orderDTOS.forEach(order -> {
            order.setImageUrl(this.orderDao.findImageUrl(order.getCommodityId()));
        });
        Long totalCount = this.orderDao.findOrderTotalCount(pageDTO);

        PageResultDTO pageResultDTO = new PageResultDTO();
        pageResultDTO.setTotalCount(totalCount);
        pageResultDTO.setDatas(orderDTOS);

        return pageResultDTO;
    }

    /**
     * 查询全部订单集合
     */
    @Override
    public List<Order> findAllOrders() {
        return this.orderDao.findAllOrders();
    }

    /**
     * 查询所有订单集合(只提取ID 和 Name)
     */
    @Override
    public List<Order> findAllOrdersWithIdName() {
        //TODO:请在此校验参数的合法性
        this.validateFindAllOrdersWithIdName();
        return this.orderDao.findAllOrdersWithIdName();
    }

    /**
     * 根据名称查询订单集合(只提取ID 和 Name)
     *
     * @param orderName 名称
     */
    @Override
    public List<Order> findOrdersWithIdNameByName(String orderName) {
        //TODO:请在此校验参数的合法性
        this.validateFindOrdersWithIdNameByName(orderName);
        //TODO:缓存取对应参数
        Set<String> keys = stringRedisTemplate.keys("searchData:Order_where_orderName_" + orderName);
        List<Order> orders = new ArrayList<>();
        if (keys.isEmpty()) {
            orders = this.orderDao.findOrdersWithIdNameByName(orderName);
            redisTemplate.opsForValue().set("searchData:Order_where_orderName_" + orderName, orders, 30, TimeUnit.DAYS);
        } else {
            orders = redisTemplate.opsForValue().get("searchData:Order_where_orderName_" + orderName);
        }
        return orders;
    }

    /**
     * 根据ID查询指定的订单(只提取ID 和 Name)
     *
     * @param orderId Id
     */
    @Override
    public Order findOrdersWithIdNameById(Long orderId) {
        //TODO:请在此校验参数的合法性
        this.validateFindOrdersWithIdNameById(orderId);
        return this.orderDao.findOrdersWithIdNameById(orderId);
    }

    /**
     * 根据ID查询指定的订单
     *
     * @param orderId Id
     */
    @Override
    public Order findOrder(Long orderId) {
        //TODO:请在此校验参数的合法性
        this.validateFindOrder(orderId);
        return this.orderDao.findOrder(orderId);
    }

    /**
     * 根据ID查询指定的订单(包含外键)
     *
     * @param orderId Id
     */
    @Override
    public Order findOrderWithForeignName(Long orderId) {
        //TODO:请在此校验参数的合法性
        this.validateFindOrderWithForeignName(orderId);
        return this.orderDao.findOrderWithForeignName(orderId);
    }

    /**
     * 新增订单
     *
     * @param order 实体对象
     */
    @Override
    public Order saveOrder(Order order) {
        //TODO:请在此校验参数的合法性
        this.validateSaveOrder(order);
        //TODO:填充公共参数
        this.setSavePulicColumns(order);
        Long rows = this.orderDao.saveOrder(order);
        if (rows != 1) {
            String error = "新增保存订单出错，数据库应该返回1,但返回了 " + rows;
            throw new BusinessException(error);
        }
        return order;
    }

    /**
     * 更新订单
     *
     * @param order 实体对象
     */
    @Override
    public Order updateOrder(Order order) {
        //TODO:请在此校验参数的合法性
        this.validateUpdateOrder(order);
        Long rows = this.orderDao.updateOrder(order);
        if (rows != 1) {
            String error = "修改保存订单出错，数据库应该返回1,但返回了 " + rows + ",数据可能被删除";
            throw new BusinessException(error);
        }
        return order;
    }

    /**
     * 根据ID删除订单
     *
     * @param orderId ID
     */
    @Override
    public void deleteOrder(Long orderId) {
        //TODO:请在此校验参数的合法性
        this.validateDeleteOrder(orderId);

        Map<Class<? extends BaseEntity>, EntityUsage> entityUsageMap = this.checkForeignEntity(Order.class, orderId);
        if (entityUsageMap != null && entityUsageMap.size() > 0) {
            StringBuilder errors = new StringBuilder();
            errors.append("计划删除的数据正在被以下数引用\n");
            for (EntityUsage entityUsage : entityUsageMap.values()) {
                errors.append("\t").append(entityUsage.getEntityLabel()).append("\n");
                for (Map.Entry<Long, String> entry : entityUsage.getUsageIdNames().entrySet()) {
                    errors.append("\t\t").append(entry.getKey()).append("\t").append(entry.getValue()).append("\n");
                }
            }
            errors.append("，不能删除，请检查处理后再删除");
            throw new BusinessException(errors.toString());
        }

        Long rows = this.orderDao.deleteOrder(orderId);
        if (rows != 1) {
            String error = "删除订单出错，数据可能已经被删除";
            throw new BusinessException(error);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public JsonResult putOrder(Order order) {
		    /*
                1、直接在数据库新增数据，在用户量较大时，会造成数据库压力过大导致异常

                2、优化
                    2.1、使用redis存储商品库存，下单时进行减去相对应库存
                    2.2、扣减库存时，通过redis校验是否超卖，超卖直接结束请求，返回结果，未超卖，执行下部分流程
                    2.3、redis扣减库存，此时返回结果，下单成功（提升用户体验），然后加入Rabbit等待，若超时未支付则库存+1，否则完成订单的提交

            */
        String commodityStockKey = order.getCommodityId()+"-"+order.getSpecification();
        Integer count = order.getCount();
        //查询不到库存则去数据进行查询，此时需要进行加锁，防止大量请求直接访问数据，其余线程发现已经加锁，则自旋，直到返回库存

        //while (this.redisUtil.get(commodityStockKey)==null){
        //    //轮询去数据库查询对应商品-规格的库存,需要进行加锁
        //    //1、加锁
        //
        //    //2、
        //        // 2.1获取到锁，去数据库进行查找库存，然后缓存到redis中
        //
        //        //2.2未获取到锁，进行自旋
        //
        //    //该方式可能造成cpu使用增加，资源浪费
        //}
        //redisson实现分布式锁
        String lockKey="putLock" + commodityStockKey;
        redissonUtil.lock(lockKey);
        logger.info(order.getUserId()+"加锁成功");
        try{
            //如果业务执行过长，Redisson会自动给锁续期
            Integer stock = (Integer) this.redisUtil.get(commodityStockKey);
            if(stock==null){
               stock=this.specificationStockService.getStock(order.getCommodityId(),order.getSpecification());
               if(stock==null){
                   stock=0;
               }
               this.redisUtil.set(commodityStockKey,stock,3600);
               logger.info("重新加载了库存"+commodityStockKey+":"+stock);
            }
            if(stock<count){
                return JsonResult.error("库存不足");
            }else{
                //redis中扣减库存
                this.redisUtil.decr(commodityStockKey,count);

                //数据库扣减库存
                Long rows=this.specificationStockService.decrStock(order.getCommodityId(),order.getSpecification(),count);
                if(rows==1) {
                    //生成订单
                    order.setCode("D" + PrimaryGenerator.getInstance().make());
                    order.setCreateDatetime(new Date());
                    order.setOrderStatus("待付款");

                    //获取用户收获地址信息
                    ShippingAddress userAddress = order.getUserAddress();
                    order.setRecipient(userAddress.getRecipient());
                    order.setPhone(userAddress.getPhone());
                    order.setDetailedAddress(userAddress.getArea()+userAddress.getDetailedAddress());
                    //保存订单
                    this.orderDao.saveOrder(order);
                    //加入Mq，进行超时未付款自动退单
                    rabbitMqUtil.sendMessage(order);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("提交订单出错了！请稍后重试");
        } finally {
            //解锁，如果业务执行完成，就不会继续续期，即使没有手动释放锁，在30秒过后，也会释放锁
            redissonUtil.unlock(lockKey);
            logger.info(order.getUserId()+"解锁成功");
        }

        return JsonResult.ok("下单成功").put("orderId",order.getEid());
    }

    @Override
    public JsonResult payOrder(Long orderId) {
        this.orderDao.payOrder(orderId);
        return JsonResult.ok("付款成功");
    }

    @Override
    public JsonResult updateOrderStatus(Long orderId, String status){
        this.orderDao.updateOrderStatus(orderId,status);
        return JsonResult.ok();
    }

    @Override
    public JsonResult confirmReceipt(Long orderId) {
        //this.orderDao.updateOrderStatus(orderId,"订单完成");
        this.orderDao.confirmReceipt(orderId);
        return JsonResult.ok("签收成功");
    }

    @Override
    public JsonResult changeAddress(Order order) {
        this.orderDao.updateAddress(order);
        return JsonResult.ok("修改成功");
    }

    @Override
    public JsonResult delivery(Long orderId) {
        this.orderDao.delivery(orderId);
        return JsonResult.ok("发货成功");
    }
    //TODO:---------------验证-------------------

    private void validateFindOrders(PageDTO pageDTO) {
        //TODO:请使用下面方法添加数据过滤条件
        //		pageDTO.addFilter("creatorId",this.getLoginUserId());
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateFindOrdersWithIdNameByName(String orderName) {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }


    private void validateFindAllOrdersWithIdName() {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateFindOrdersWithIdNameById(Long orderId) {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateFindOrder(Long orderId) {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateFindOrderWithForeignName(Long orderId) {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateSaveOrder(Order order) {
        //不为空判断
        if (order.getEid() != null || order.getCreatorId() != null || order.getCreateDatetime() != null) {
            throw new BusinessException("非法请求");
        }
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateUpdateOrder(Order order) {
        //不为空判断
        if (order.getEid() == null) {
            throw new BusinessException("唯一标识不能为空");
        }
        //是否存在判断
        if (this.orderDao.findOrderTotalCount(PageDTO.create(Order.FIELD_ID, order.getEid())) == 0) {
            throw new BusinessException("修改的订单 " + order.getName() + " 不存在，修改失败，请重试或联系管理员");
        }
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    private void validateDeleteOrder(Long orderId) {
        //TODO:请完善数据校验规则和数据权限判断，如果有问题请抛出异常，参看下面validateUpdateOrder()写法
    }

    @Override
    public boolean canDownloadAttachment(String formName, Long id) {
        return true;
    }
}
